/***
录音 RecordApp: uni-app支持文件，支持 H5、App vue、App nvue、微信小程序
GitHub、详细文档、许可及服务协议: https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp

DCloud插件地址：https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore
App配套原生插件：https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin

全局配置参数：
	RecordApp.UniAppUseLicense:"" App中使用的授权许可，获得授权后请赋值为"我已获得UniAppID=***的商用授权"（***为你项目的uni-app应用标识），设置了UniNativeUtsPlugin时默认为已授权；如果未授权，将会在App打开后第一次调用`RecordApp.RequestPermission`请求录音权限时，弹出“未获得商用授权时，App上仅供测试”提示框。
	
	RecordApp.UniNativeUtsPlugin:null App中启用原生录音插件或uts插件，由App提供原生录音，将原生插件或uts插件赋值给这个变量即可开启支持；使用原生录音插件只需赋值为{nativePlugin:true}即可（提供nativePluginName可指定插件名字，默认为Recorder-NativePlugin），使用uts插件只需import插件后赋值即可（uts插件还未开发，目前不可集成）；如果未提供任何插件，App中将使用H5录音（在renderjs中提供H5录音）。
	
	RecordApp.UniWithoutAppRenderjs:false 不要使用或没有renderjs时，应当设为true，此时App中RecordApp完全运行在逻辑层，比如nvue页面，此时音频编码之类的操作全部在逻辑层，需要提供UniNativeUtsPlugin配置由原生插件进行录音，可视化绘制依旧可以在renderjs中进行。默认为false，RecordApp将在renderjs中进行实际的工作，然后将处理好的数据传回逻辑层，数据比较大时传输会比较慢。

不同平台环境下使用说明：
	【H5】 引入RecordApp和本js，按RecordApp的文档使用即可，和普通网页开发没有区别

	【微信小程序】 引入RecordApp和本js，同时引入RecordApp中的app-miniProgram-wx-support.js即可，录音操作和H5完全相同，其他可视化扩展等使用请参考RecordApp中的小程序说明
	
	【App vue】 引入RecordApp和本js，并创建一个<script module="xxx" lang="renderjs">，在renderjs中也引入RecordApp和本js，录音操作和H5大部分相同，部分回调需要多编写一个renderjs的处理代码，比如onProcess_renderjs，具体的请参考RecordApp文档中的app-support-sample/demo_UniApp文档
	
	【App nvue】 引入RecordApp和本js，配置RecordApp.UniWithoutAppRenderjs=true 和提供RecordApp.UniNativeUtsPlugin，录音操作和H5完全相同，但不支持可视化扩展
***/
(function(factory) {
	var browser = typeof window == "object" && !!window.document;
	var win = browser ? window : Object; //非浏览器环境，Recorder挂载在Object下面
	var DocUrl = "https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp";
	if (!win.RecordApp) {
		console.error(`需要先引入RecordApp，请按下面代码引入：
1. 项目根目录 npm install recorder-core
2. 页面中按顺序import
import Recorder from 'recorder-core'
import RecordApp from 'recorder-core/src/app-support/app.js'
import 你需要的音频格式编码器、可视化插件
参考文档：${DocUrl}`);
		return;
	}
	var rec = win.Recorder,
		ni = rec.i18n;
	factory(DocUrl, rec, ni, ni.$T, browser);
}(function(DocUrl, Recorder, i18n, $T, isBrowser) {
	"use strict";

	var RecordApp = Recorder.RecordApp;
	var CLog = RecordApp.CLog,
		NOOP = function() {};
	RecordApp.UniSupportLM = "2024-06-25 00:37";
	var JsName = "app-uni-support.js";

	var isApp = false,
		isAppIOS = false,
		isAppMain = false,
		isH5 = false,
		isMpWx = false;
	// #ifdef APP
	if (!isBrowser) {
		if (typeof plus == "object") {
			if (plus.os.name == "Android") {
				isApp = true;
			} else if (plus.os.name == "iOS") {
				isApp = true;
				isAppIOS = true;
			}
		}
		isAppMain = isApp; //是app逻辑层
		if (!isApp) {
			CLog("App !plus", 1);
		}
	} else {
		isApp = true;
		var ua = navigator.userAgent.replace(/[_\d]/g, " ");
		isAppIOS = !/\bandroid\b/i.test(ua) && /\bios\b|\biphone\b/i.test(ua)
	}
	// #endif
	if (!isApp) { //条件编译在renderjs中无效？
		// #ifdef H5
		isH5 = true;
		// #endif
		// #ifdef MP-WEIXIN
		isMpWx = true;
		// #endif
	}

	//判断当前环境是否是app，非app返回0，Android返回1，iOS返回2
	RecordApp.UniIsApp = function() {
		return isApp ? isAppIOS ? 2 : 1 : 0;
	}



	//base64编码，提供arrayBuffer
	var UniBtoa = RecordApp.UniBtoa = function(buffer) {
		//以前btoa app里面测试有bug "AB\u00ce\u00a6CD" != "QULOpkNE"
		if (typeof uni == "object" && uni.arrayBufferToBase64) {
			return uni.arrayBufferToBase64(buffer);
		} else {
			var u8arr = new Uint8Array(buffer);
			var str = "";
			for (var i = 0, L = u8arr.length; i < L; i++) {
				str += String.fromCharCode(u8arr[i]);
			}
			return btoa(str);
		}
	}
	//base64解码，返回arrayBuffer
	var UniAtob = RecordApp.UniAtob = function(b64) {
		if (typeof uni == "object" && uni.base64ToArrayBuffer) {
			return uni.base64ToArrayBuffer(b64);
		} else {
			var str = atob(b64),
				u8arr = new Uint8Array(str.length);
			for (var i = 0, L = str.length; i < L; i++) {
				u8arr[i] = str.charCodeAt(i);
			}
			return u8arr.buffer;
		}
	}

	//base64编码，提供字符串
	RecordApp.UniB64Enc = function(str) {
		if (typeof uni == "object" && uni.arrayBufferToBase64) {
			var buf = RecordApp.UniStr2Buf(str);
			return uni.arrayBufferToBase64(buf);
		}
		return btoa(unescape(encodeURIComponent(str)));
	}
	//base64解码，返回字符串
	RecordApp.UniB64Dec = function(b64) {
		if (typeof uni == "object" && uni.base64ToArrayBuffer) {
			var buf = uni.base64ToArrayBuffer(b64);
			return RecordApp.UniBuf2Str(buf);
		}
		return decodeURIComponent(escape(atob(b64)));
	}
	//文本转成arrayBuffer
	RecordApp.UniStr2Buf = function(str) {
		var s = unescape(encodeURIComponent(str));
		var arr = new Uint8Array(s.length);
		for (var i = 0, L = s.length; i < L; i++) arr[i] = s.charCodeAt(i);
		return arr.buffer;
	}
	//arrayBuffer转成文本
	RecordApp.UniBuf2Str = function(buffer) {
		var arr = new Uint8Array(buffer),
			s = "";
		for (var i = 0, L = arr.length; i < L; i++) s += String.fromCharCode(arr[i]);
		return decodeURIComponent(escape(s));
	}



	var JsSource = RecordApp.UniJsSource = {
		IsSource: true //@@IsSource 源码版
			,
		pcm_sum: function(pcm) { //优化循环性能
			var sum = 0;
			for (var i = 0; i < pcm.length; i++) {
				sum += Math.abs(pcm[i]);
			}
			return sum;
		}
	};
	/*@@Source Start@@*/

	var platform = {
		Support: function(call) {
			if (isMpWx) {
				if (!RecordApp.Platforms["miniProgram-wx"]) {
					CLog($T("RXs7::微信小程序中需要：{1}", 0,
							"import 'recorder-core/src/app-support/app-miniProgram-wx-support.js'"),
						1);
				}
				call(false);
				return; //由微信小程序支持文件提供录音功能
			}
			if (isH5) {
				call(false);
				return; //由RecordApp的Default-H5提供录音支持
			}
			if (!isApp) {
				CLog($T("4ATo::Recorder-UniCore目前只支持：H5、APP(Android iOS)、MP-WEIXIN，其他平台环境需要自行编写适配文件实现接入"),
					3);
				call(false);
				return; //不支持此平台
			}
			if (isBrowser) { //在renderjs中
				if (!RecordApp.UniAppUseNative) {
					call(false);
					return; //App未提供原生插件，使用H5进行录音
				}
			}
			call(true); //由renderjs提供支持，或者由原生插件提供支持
		},
		CanProcess: function() {
			return true; //支持实时回调
		}
	};
	RecordApp.RegisterPlatform(isBrowser ? "UniApp-Renderjs" : "UniApp-Main", platform);
	if (isApp) {
		CLog.Tag = isBrowser ? "RecApp Renderjs" : "RecApp Main";
	}
	//全局数据存储
	if (!RecordApp.__UniData) {
		RecordApp.__UniData = {
			id: 0,
			pageShow: {}
		};
	}


	//当前是否需要在renderjs中进行录音处理
	var UseWebView = function() {
		return isApp && !isBrowser && !RecordApp.UniWithoutAppRenderjs;
	};
	//所有平台逻辑层中调用，当使用到录音的页面onShow时反复进行调用，传入当前页面或组件的this对象
	RecordApp.UniPageOnShow = function(componentThis) {
		var psData = RecordApp.__UniData.pageShow = {};
		if (isMpWx && RecordApp.MiniProgramWx_onShow) { //微信小程序需要调用onShow
			RecordApp.MiniProgramWx_onShow();
		}
		if (!UseWebView()) return;
		psData.sWvCid = GetCp_WvCid(componentThis);

		var wvId = RecordApp.__uniAppWebViewId;
		if (wvId) { //移除已关闭了的页面或组件
			var ps = getCurrentPages(),
				isClosed = true;
			for (var i = 0, L = ps.length; i < L; i++) { //@@Fast
				if (ps[i].$page.id == wvId) {
					isClosed = false;
					break;
				}
			}
			if (isClosed) {
				RecordApp.__uniAppComponentId = null;
				RecordApp.__uniAppWebViewId = null;
				RecordApp.__uniAppWebView = null;
			}
		}
	};
	//App 逻辑层中调用，切换使用当前页面或组件的WebView，App环境中在调用本方法后才可以进行RecordApp.RequestPermission、RecordApp.Start操作
	RecordApp.UniWebViewActivate = function(componentThis) {
		if (!UseWebView()) return;
		RecordApp.__hasWvActivate = true;
		RecordApp.__callWvActivate = 1; //代表在当前调用过程中，脱离了就为0
		setTimeout(function() {
			RecordApp.__callWvActivate = 0
		});

		var page = componentThis.$root;
		if (!page || !page.$scope || !page.$scope.$getAppWebview) {
			CLog($T("GwCz::RecordApp.UniWebViewActivate 需要传入当前页面或组件的this对象作为参数"), 1);
			return;
		}
		//当前调用的组件id
		var cid = componentThis._$id || componentThis.$ && componentThis.$
		.uid; // github:dcloudio/uni-app /src/platforms/app-plus/helpers/util.js
		//获取当前页面的renderjs WebView id
		var id = GetCp_WvId(componentThis);
		if (!id || !cid) {
			CLog($T("ipB3::RecordApp.UniWebViewActivate 发生不应该出现的错误（可能需要升级插件代码）：") + "!id || !cid", 1);
			return;
		}
		if (cid != RecordApp.__uniAppComponentId || id != RecordApp.__uniAppWebViewId) {
			CLog($T("WpKg::RecordApp.UniWebViewActivate 已切换当前页面或组件的renderjs所在的WebView") + " WvCid=wv_" +
				id + "_cid_" + cid);
		}
		RecordApp.__uniAppComponentId = cid;
		if (RecordApp.__uniAppWebViewId != id) { //缓存起来，相同的webview不用反复获取
			RecordApp.__uniAppWebViewId = id;
			RecordApp.__uniAppWebView = page.$scope.$getAppWebview();
		}
	};
	//App renderjs中的mounted内调用，传入当前模块的this，一个renderjs模块只需调用一次即可
	RecordApp.UniRenderjsRegister = function(rModule) {
		if (!isApp || !isBrowser) return;
		if (rModule.$ownerInstance) {
			var id = window.__WebVieW_Id__,
				cid = rModule._$id || rModule.$ownerInstance.$vm.ownerId,
				el = rModule.$ownerInstance.$el;
		}
		if (!el) {
			CLog($T("4jKV::RecordApp.UniRenderjsRegister 需在renderjs中调用并且传入当前模块的this"), 1);
			return;
		}
		el.__rModule = rModule;
		if (!id || !cid) {
			CLog($T("Uc9E::RecordApp.UniRenderjsRegister 发生不应该出现的错误（可能需要升级插件代码）：") + "!id || !cid", 1);
			return;
		}
		var wvCid = "wv_" + id + "_cid_" + cid;
		RecordApp.__UniWvCid = wvCid;
		el.setAttribute("rec_wv_cid_key", wvCid);
		if (RecordApp.__UniData[wvCid]) {
			CLog($T("mzKj::RecordApp.UniRenderjsRegister 重复注册当前页面renderjs模块，一个组件内只允许一个renderjs模块进行注册") +
				" wvCid=" + wvCid, 3);
		} else {
			RecordApp.__UniData[wvCid] = 1;
			CLog($T("7kJS::RecordApp.UniRenderjsRegister 已注册当前页面renderjs模块") + " WvCid=" + wvCid);
		}
	};
	//请求权限时，将当前组件或页面的 WebVew id + 组件id 标识保存起来，后续操作都使用这个标识
	var lockReqWvCid = function() {
		RecordApp.__uniAppReqWebViewId = RecordApp.__uniAppWebViewId;
		RecordApp.__uniAppReqComponentId = RecordApp.__uniAppComponentId;
	};
	//App逻辑层中获取组件或页面的 WebVew id + 组件id 标识，没有获取到时，如果提供了useAct将尝试返回当前切换的页面或组件的标识，否则返回空字符串
	var GetCp_WvCid = function(cp, useAct, disCheck) {
		if (cp) {
			if (cp.Rec_WvCid) return cp.Rec_WvCid;
			var id = GetCp_WvId(cp),
				cid = cp._$id || cp.$ && cp.$.uid;
		}
		if (useAct) {
			if (!disCheck) Check_ActWv();
			if (useAct == "@req") {
				var id = RecordApp.__uniAppReqWebViewId,
					cid = RecordApp.__uniAppReqComponentId;
			} else {
				var id = RecordApp.__uniAppWebViewId,
					cid = RecordApp.__uniAppComponentId;
			}
		}
		if (id && cid) {
			return "wv_" + id + "_cid_" + cid;
		}
		return "";
	};
	//使用 WebVew id + 组件id 标识伪装成组件，可获得对应的WebView进行操作，标识为@act时返回当前切换的页面或组件，为@req时返回当前请求了权限的页面或组件
	var WvCid_to_Cp = function(wvCid) {
		if (wvCid === "@act" || wvCid === "@req") {
			return {
				Rec_WvCid: GetCp_WvCid(null, wvCid)
			};
		} else {
			return {
				Rec_WvCid: wvCid || "?"
			};
		}
	};
	//App逻辑层中获取组件或页面的WebView id，没有获取到返回0
	var GetCp_WvId = function(
	cp) { //https://github.com/dcloudio/uni-app/blob/cfc247d675157b31eda61fc7420fad0cd0b96dbd/src/platforms/app-plus/service/framework/page.js#L170
		var page = cp.$root;
		page = page && page.$page;
		return page && page.id || 0;
	};
	//App逻辑层中获取组件或页面的WebView对象，没有获取到时，如果提供了useAct将尝试返回当前切换的页面或组件的WebView对象，否则返回null
	var GetCp_WvObj = function(cp, useAct) {
		if (cp && cp.Rec_WvCid) {
			var m = /^wv_(\d+)_/.exec(cp.Rec_WvCid);
			var id = m && m[1];
		} else {
			var page = cp && cp.$root,
				sc = page && page.$scope;
			page = page && page.$page;
			var id = page && page.id;
		}
		if (id) {
			if (id == RecordApp.__uniAppWebViewId) return RecordApp.__uniAppWebView;
			if (sc) return sc.$getAppWebview();
			var webview = plus.webview.getWebviewById(id);
			if (webview) return webview;
		}
		if (useAct) {
			Check_ActWv();
			return RecordApp.__uniAppWebView;
		}
		return null;
	};
	//检查UniWebViewActivate获取到的WebView是否可用，不可用将会返回错误消息
	var Check_ActWv = function(checkReq) {
		if (!RecordApp.__uniAppWebView) return $T("AGd7::需要先调用RecordApp.UniWebViewActivate方法");
		var wvCid = GetCp_WvCid(null, 1, 1),
			sWvCid = RecordApp.__UniData.pageShow.sWvCid;
		if (checkReq) {
			if (!RecordApp.__uniAppReqWebViewId) return $T("7ot0::需先调用RecordApp.RequestPermission方法");
			if (GetCp_WvCid(null, "@req", 1) != wvCid) return $T(
			"VsdN::需重新调用RecordApp.RequestPermission方法");
		}
		if (sWvCid && sWvCid != wvCid) {
			CLog($T("SWsy::检测到有其他页面或组件调用了RecordApp.UniPageOnShow（WvCid={1}），但未调用过RecordApp.UniWebViewActivate（当前WvCid={2}），部分功能会继续使用之前Activate的WebView和组件，请确保这是符合你的业务逻辑，不是因为忘记了调用UniWebViewActivate",
				0, sWvCid, wvCid), 3);
		}
		return "";
	};


	//app逻辑层中直接调用此页面或组件的WebView renderjs中的eval（componentThis为null时使用UniWebViewActivate切换的页面或组件）；要调用renderjs模块vue组件内的方法请用UniWebViewVueCall；如果需要传递大的数据请用bigBytes参数传入一个ArrayBuffer，jsCode中使用BigBytes变量得到这个数据
	RecordApp.UniWebViewEval = function(componentThis, jsCode, bigBytes) {
		var err = "";
		if (!err && !isAppMain) {
			err = $T("TfJX::当前不是App逻辑层");
		}
		var webview = !err && GetCp_WvObj(componentThis, componentThis == null);
		if (!err && !webview) {
			if (componentThis == null) {
				err = $T("peIm::当前还未调用过RecordApp.UniWebViewActivate");
			} else {
				err = $T("qDo1::未找到此页面renderjs所在的WebView");
			}
		}
		if (err) {
			err += $T("igw2::，不可以调用RecordApp.UniWebViewEval");
			CLog(err + "   jsCode=" + jsCode.substr(0, 200), 1);
			return err;
		}

		var psData = RecordApp.__UniData.pageShow;
		if (!psData.mrBind) {
			psData.mrBind = 1;
			MainReceiveBind(); //切换了页面，尝试重新绑定renderjs回调
		}

		if (!bigBytes) {
			webview.evalJS('(function(){\n' + jsCode + '\n})()');
			return;
		}
		//将大的二进制数据先分段发送到renderjs
		var rndId = ("a" + Math.random()).replace(".", "");
		var idx = 0,
			blockSize = 512 * 1024;
		var run = function() {
			if (idx != 0 && idx >= bigBytes.byteLength) {
				webview.evalJS(`(function(){
				var BigBytes=window.${rndId}.memory.buffer; delete window.${rndId};
				${jsCode}
			})()`);
				return;
			}
			var cb = RecordApp.UniMainCallBack(run);
			var start = idx;
			idx += blockSize;
			var buf = bigBytes.slice(start, idx);
			webview.evalJS(`(function(){
			var cur=window.${rndId}=window.${rndId}||{memory:new Uint8Array(${bigBytes.byteLength}),mOffset:0};
			var buf=new Uint8Array(RecordApp.UniAtob("${UniBtoa(buf)}"));
			cur.memory.set(buf,cur.mOffset);
			cur.mOffset+=buf.byteLength;
			RecordApp.UniWebViewSendToMain({action:"${cb}"});
		})()`);
		};
		run();
	};

	//app逻辑层中直接调用此页面或组件的renderjs模块vue组件内的方法（componentThis为null时使用UniWebViewActivate切换的页面或组件），jsCode中的this为renderjs模块的this（也可以用This变量）（如需renderjs中调用逻辑层vue实例方法，请直接用$ownerInstance.callMethod即可）；如果需要传递大的数据请用bigBytes参数传入一个ArrayBuffer，jsCode中使用BigBytes变量得到这个数据
	RecordApp.UniWebViewVueCall = function(componentThis, jsCode, bigBytes) {
		// github:dcloudio/uni-app /src/core/view/plugins/wxs/component-descriptor.js
		//		/src/platforms/app-plus/service/framework/subscribe-handlers/on-wxs-invoke-call-method.js
		var preCode = "";
		if (typeof jsCode == "object") {
			preCode = jsCode.preCode || "";
			jsCode = jsCode.jsCode || "";
		};

		var err = "";
		if (!err && !isAppMain) {
			err = $T("lU1W::当前不是App逻辑层");
		}
		var wvCid = !err && GetCp_WvCid(componentThis, componentThis == null);
		if (!err && !wvCid) {
			if (componentThis == null) {
				err = $T("mSbR::当前还未调用过RecordApp.UniWebViewActivate");
			} else {
				err = $T("6Iql::未找到此页面renderjs所在的WebView Cid");
			}
		}
		if (err) {
			err += $T("TtoS::，不可以调用RecordApp.UniWebViewVueCall");
			CLog(err + "   jsCode=" + jsCode.substr(0, 200), 1);
			return err;
		}
		RecordApp.UniWebViewEval(componentThis, `(function(){
		var CallErr=function(){};
		${preCode}
		if(!window.RecordApp){
			var err=${JSON.stringify($T("U1Be::renderjs中未import导入RecordApp"))};
			window["console"].error(err); CallErr(err); return;
		};
		var wvCid="${wvCid}",vm=RecordApp.__uniWvCallVm;
		if(!vm || RecordApp.__uniWvCallWvCid!=wvCid){
			if(!RecordApp.__UniData[wvCid]){
				var err=${JSON.stringify($T("Bcgi::renderjs中的mounted内需要调用RecordApp.UniRenderjsRegister"))};
				RecordApp.CLog(err,1); CallErr(err); return;
			};
			var el=document.querySelector("[rec_wv_cid_key='"+wvCid+"']");
			vm=el&&el.__rModule;
			if(!vm){
				var err=${JSON.stringify($T("URyD::没有找到组件的renderjs模块"))}+" WvCid="+wvCid;
				RecordApp.CLog(err,1); CallErr(err); return;
			};
			RecordApp.__uniWvCallVm=vm;
			RecordApp.__uniWvCallWvCid=wvCid;
		}; (function(){ var This=this;
			${jsCode}
		}).call(vm);
	})()`, bigBytes);
	};

	//app逻辑层中简化调用 UniWebViewVueCall（默认）、UniWebViewEval（set.useEval时调用） ，并异步返回调用结果，出错会抛异常；set.timeout默认5秒超时，-1不超时；jsCode中通过调用CallSuccess(value,bigBytes)、CallFail(errMsg)返回结果给逻辑层
	RecordApp.UniWebViewCallAsync = function(componentThis, set, jsCode, bigBytes) {
		return new Promise(function(resolve, reject) {
			set = set || {};
			var tag = set.tag || "";
			var noOut = set.timeout == -1,
				noOutCB = ""; //不超时，但要检测js提交超时
			var timer = setTimeout(function() {
				cbClear();
				timer = 0;
				var e = new Error(noOut ? $T("KQhJ::{1}连接renderjs超时", 0, tag) : $T(
					"RDcZ::{1}处理超时", 0, tag));
				e.isTimeout = 1;
				reject(e);
			}, noOut ? 2000 : set.timeout || 5000);

			var cbClear = function() {
				var Data = RecordApp.__UniData;
				delete Data[cb];
				delete Data[noOutCB];
			};
			if (noOut) {
				noOutCB = RecordApp.UniMainCallBack(function() {
					clearTimeout(timer);
				});
			};
			var recCB = function(data) {
				cbClear();
				if (!timer) return;
				clearTimeout(timer);
				timer = 0;
				if (data.dataID) return resolve({
					value: data.value,
					bigBytes: RecordApp.UniMainTakeBigBytes(data.dataID)
				});
				if (data.isOk) return resolve(data.value);
				reject(new Error(tag + data.errMsg));
			};
			var cb = RecordApp.UniMainCallBack(recCB);
			var code = `
		var CallSuccess=function(val,buf){
			if(buf){
				RecordApp.UniWebViewSendBigBytesToMain(buf,function(dataID){
					RecordApp.UniWebViewSendToMain({action:"${cb}", isOk:true, value:val, dataID:dataID});
				},CallFail)
			}else{
				RecordApp.UniWebViewSendToMain({action:"${cb}", isOk:true, value:val});
			}
		};
		var CallFail=function(err){
			UniViewJSBridge.publishHandler("${MainReceiveKey}",{action:"${cb}",errMsg:err});
		};` + (noOut ? `
			UniViewJSBridge.publishHandler("${MainReceiveKey}",{action:"${noOutCB}"});
		` : '') + `
		if(!window.RecordApp){
			return CallFail(${JSON.stringify($T("TSmQ::需要在页面中提供一个renderjs，在里面import导入RecordApp、录音格式编码器、可视化插件等"))});
		};
		if(!RecordApp.Platforms["UniApp-Renderjs"]){
			return CallFail(${JSON.stringify($T('AN0e::需在renderjs中import {1}',0,'"@/uni_modules/Recorder-UniCore/'+JsName+'"'))});
		};
	`;
			if (set.useEval) {
				code = code + jsCode;
			} else {
				code += 'CallErr=function(err){ CallFail(err) };';
				code = {
					preCode: code,
					jsCode: jsCode
				};
			};
			var err = RecordApp[set.useEval ? "UniWebViewEval" : "UniWebViewVueCall"](componentThis,
				code, bigBytes);
			if (err) { //执行js准备失败
				recCB({
					errMsg: err
				});
			};
		})
	};



	//绑定App renderjs层发送数据回逻辑层
	var MainReceiveKey = "RecordApp__uniAppMainReceive";
	var MainReceiveBind = function() {
		if (!isAppMain) return; //只在app逻辑层进行绑定
		if (typeof UniServiceJSBridge == "undefined") return;

		var syncId = RecordApp.__uniAppMainReceiveBind; //全局只绑定一次
		if (syncId) {
			var old = "";
			try {
				old = uni.getStorageSync(MainReceiveKey);
			} catch (e) {}
			if (syncId == old) {
				return;
			}
			CLog($T("vEgr::不应该出现的MainReceiveBind重复绑定"), 3);
		}
		syncId = "r" + Math.random();
		try {
			uni.setStorageSync(MainReceiveKey, syncId);
		} catch (e) {}
		RecordApp.__uniAppMainReceiveBind = syncId;

		UniServiceJSBridge.unsubscribe(MainReceiveKey);
		UniServiceJSBridge.subscribe(MainReceiveKey, function(data) {
			var action = data.action || "";
			if (action == "recProcess") {
				onRecProcess(data);
				return;
			}
			if (action == "recEncodeChunk") {
				onRecEncodeChunk(data);
				return;
			}
			if (action.indexOf("mainCb_") != -1) {
				var fn = RecordApp.__UniData[action];
				if (fn) fn(data);
				return;
			}
			if (action.indexOf("bigBytes_") != -1) {
				RecordApp.__UniMainReceiveBigBytes(data);
				return;
			}
			CLog($T("ZHwv::[MainReceive]从renderjs发回未知数据：") + JSON.stringify(data), 1);
		});
	}
	//逻辑层生成一个回调，renderjs层返回数据后调用这个回调，函数参数为renderjs层返回的数据，数据应当是个对象，错误消息统一用errMsg属性
	RecordApp.UniMainCallBack = function(fn) {
		var Data = RecordApp.__UniData;
		var id = ++Data.id;
		var cb = "mainCb_" + id;
		Data[cb] = function(data) {
			delete Data[cb];
			fn(data);
		};
		return cb;
	};
	//renderjs层调用本方法，将数据传回给逻辑层回调，data:{action:"UniMainCallBack等",...需要返回的数据}
	RecordApp.UniWebViewSendToMain = function(data) {
		UniViewJSBridge.publishHandler(MainReceiveKey, data);
	};


	//renderjs层调用本方法，将超过512KB的二进制数据传回逻辑层，一次性发送1MB以上的数据UniApp太卡，传入arrayBuffer，True(dataID), False(errMsg)，成功后可在逻辑层中通过UniMainTakeBigBytes(dataID)来取到数据
	RecordApp.UniWebViewSendBigBytesToMain = function(aBuf, True, False) {
		if (!isBrowser || !isApp) {
			False($T("MujG::只允许在renderjs中调用RecordApp.UniWebViewSendBigBytesToMain"));
			return;
		}
		var wvCid = RecordApp.__UniWvCid;
		if (!wvCid) {
			False($T(
				"kE91::renderjs中的mounted内需要调用RecordApp.UniRenderjsRegister才能调用RecordApp.UniWebViewSendBigBytesToMain"));
			return;
		}
		var Data = RecordApp.__UniData,
			mainID = 0,
			wvID = ++Data.id;
		Data["bigBytes_" + wvID] = function(mid) {
			mainID = mid;
			run();
		};

		var idx = 0,
			blockSize = 512 * 1024;
		var run = function() {
			if (idx != 0 && idx >= aBuf.byteLength) {
				delete Data["bigBytes_" + wvID];
				True(mainID);
				return;
			}
			var start = idx;
			idx += blockSize;
			var buf = aBuf.slice(start, idx);
			RecordApp.UniWebViewSendToMain({
				action: start ? "bigBytes_chunk" : "bigBytes_start",
				wvCid: wvCid,
				wvID: wvID,
				mainID: mainID,
				b64: UniBtoa(buf)
			});
		};
		run();
	};
	//逻辑层接收到传回的二进制数据
	RecordApp.__UniMainReceiveBigBytes = function(data) {
		var id = data.mainID,
			Data = RecordApp.__UniData,
			key = "bigBytes_",
			mSize = 2 * 1024 * 1024;
		if (data.action == "bigBytes_start") {
			id = ++Data.id;
			Data[key + id] = {
				memory: new Uint8Array(mSize),
				mOffset: 0
			};
		}

		var cur = Data[key + id];
		if (!cur) {
			CLog($T("CjMb::无效的BigBytes回传数据"), 3);
			return;
		}
		var buf = new Uint8Array(UniAtob(data.b64));
		var bufLen = buf.length;
		if (cur.mOffset + bufLen > cur.memory.length) {
			var tmp = new Uint8Array(cur.memory.length + Math.max(mSize, bufLen));
			tmp.set(cur.memory.subarray(0, cur.mOffset));
			cur.memory = tmp;
		}
		cur.memory.set(buf, cur.mOffset);
		cur.mOffset += bufLen;

		RecordApp.UniWebViewEval(WvCid_to_Cp(data.wvCid), `(function(){
		var fn=RecordApp.__UniData["${key}${data.wvID}"];
		if(fn)fn(${id});
	})()`);
	};
	//逻辑层取走接收到的二进制数据，返回arrayBuffer，不可重复提取否则返回null
	RecordApp.UniMainTakeBigBytes = function(dataID) {
		if (!isAppMain) return null;
		var Data = RecordApp.__UniData;
		var cur = Data["bigBytes_" + dataID];
		delete Data["bigBytes_" + dataID];
		if (!cur) return null;
		return cur.memory.buffer.slice(0, cur.mOffset);
	};



	//App逻辑层、小程序环境下保存文件到本地，提供文件名和arrayBuffer，True(savePath)，False(errMsg)
	RecordApp.UniSaveLocalFile = function(fileName, buffer, True, False) {
		True = True || NOOP;
		False = False || NOOP;
		var fail = function(e) {
			False($T("UqfI::保存文件{1}失败：", 0, fileName) + (e.message || e.errMsg));
		}
		if (isMpWx) {
			var savePath = wx.env.USER_DATA_PATH + "/" + fileName;
			wx.getFileSystemManager().writeFile({
				filePath: savePath,
				encoding: "binary",
				data: buffer,
				success: function() {
					True(savePath)
				},
				fail: fail
			});
		} else if (isAppMain) {
			plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, function(fs) {
				fs.root.getFile(fileName, {
					create: true
				}, function(file) {
					file.createWriter(function(writer) {
						writer.onwrite = function() {
							True(file.fullPath);
						};
						writer.onerror = fail;
						try {
							writer.writeAsBinary(UniBtoa(buffer));
						} catch (e) {
							fail(e)
						}
					}, fail);
				}, fail);
			}, fail);
		} else {
			False($T("kxOd::当前环境未支持保存本地文件"));
		}
	}


	//检查配置值，部分消息只显示一次
	var LogCheckConfig = function(req) {
		nativeUtsPluginCheck(); //检查原生插件配置是否有效
		if (!isAppMain) return; //只在app逻辑层检查

		var docTips = $T("1f2V:: | RecordApp的uni-app支持文档和示例：{1} ", 0, DocUrl);
		var nup = RecordApp.UniNativeUtsPlugin;
		if (nup) {
			if (req || !RecordApp.__nnM6) {
				RecordApp.__nnM6 = 1;
				if (nup.nativePlugin) {
					CLog($T("XSYY::当前录音由原生录音插件提供支持") + docTips);
				} else {
					CLog($T("nnM6::当前录音由uts插件提供支持") + docTips);
				}
			}
		}
		if (RecordApp.UniWithoutAppRenderjs) {
			if (!nup) {
				CLog($T("fqhr::当前已配置RecordApp.UniWithoutAppRenderjs，必须提供原生录音插件或uts插件才能录音，请参考RecordApp.UniNativeUtsPlugin配置") +
					docTips, 1);
			} else {
				if (req || !RecordApp.__xYRb) {
					RecordApp.__xYRb = 1;
					CLog($T("xYRb::当前RecordApp运行在逻辑层中（性能会略低一些，可视化等插件不可用）") + docTips);
				}
			}
		} else if (RecordApp.__hasWvActivate) {
			if (!RecordApp.__uniAppWebView) {
				CLog($T("S3eF::未找到当前页面renderjs所在的WebView，如果确实没有renderjs，请设置RecordApp.UniWithoutAppRenderjs=true") +
					docTips, 1);
			} else {
				if (req || !RecordApp.__0hyi) {
					RecordApp.__0hyi = 1;
					CLog($T("0hyi::当前RecordApp运行在renderjs所在的WebView中（逻辑层中只能做有限的实时处理，可视化等插件均需要在renderjs中进行调用）") +
						docTips);
				}
			}
		}
	}
	//检查授权许可
	var LogCheckLicense = function(call) {
		if (!isAppMain) {
			call();
			return
		}
		var info = RecordApp.__FabE = RecordApp.__FabE || {};
		var end = function(msg, color, notCall) {
			CLog($T("FabE::【在App内使用{1}的授权许可】", 0, JsName) + msg, color || 0);
			if (!notCall) call();
			return;
		};
		var nup = RecordApp.UniNativeUtsPlugin;
		if (nup || info.uts) {
			info.uts = info.uts || (nup.nativePlugin ? 2 : 1);
			if (info.uts == 2) {
				end($T("w37G::已购买原生录音插件，获得授权许可"));
			} else {
				end($T("e71S::已购买uts插件，获得授权许可"));
			}
			return;
		}
		var appId = info.appId = info.appId || uni.getSystemInfoSync().appId || "0";
		var licenseTxt = "我已获得UniAppID=" + appId + "的商用授权";
		if (RecordApp.UniAppUseLicense) {
			if (RecordApp.UniAppUseLicense == licenseTxt) {
				end(licenseTxt);
				return;
			}
			CLog($T("aPoj::UniAppUseLicense填写无效，如果已获取到了商用授权，请填写：{1}，否则请使用空字符串", 0, licenseTxt), 3);
		}
		if (true) { //@@IsSource 源码版
			end("当前为源码版，" + licenseTxt);
			return;
		}
		//未获得授权弹框显示
		var noEnd = function(notCall) {
			end("当前未获得授权许可。" +
				"文件" + JsName +
				"在uni-app中编译到App平台时仅供测试用（App平台包括：Android App、iOS App），不可用于正式发布或商用，正式发布或商用需先获取到商用授权许可（编译到其他平台时无此授权限制，比如：H5、小程序，均为免费授权）。" +
				"未获得授权时，在App打开后第一次调用RecordApp.RequestPermission请求录音权限时，会先弹出商用授权提示框；" +
				"获取到授权许可后，请在调用RequestPermission前设置 RecordApp.UniAppUseLicense='" + licenseTxt +
				"' ，就不会弹提示框了；" +
				"或者购买了配套的原生录音插件，设置RecordApp.UniNativeUtsPlugin参数后，也不会弹提示框。" +
				"【获取授权方式】到DCloud插件市场购买授权: " + NpDcUrl +
				"-Android （会赠送Android版原生插件）；购买后可联系客服，同时提供订单信息，客服拉你进入VIP支持QQ群，入群后在群文件中可下载此js文件最新源码；" +
				"客服联系方式：QQ 1251654593 ，或者直接联系作者QQ 753610399 （回复可能没有客服及时）。" +
				"详细请参考文档: " + DocUrl +
				" ", 3, notCall);
		}
		if (info.show) {
			noEnd();
			return;
		}
		noEnd(1);
		info.show = 1;
		uni.showModal({
			title: "未获得商用授权时，App上仅供测试哈",
			content: "文件" + JsName +
				"在uni-app中编译到App平台时仅供测试用，不可用于正式发布或商用，正式发布或商用需先获得授权许可（编译到其他平台时无此授权限制，比如：H5、小程序，均为免费授权）。本对话框仅在第一次请求录音权限时会弹出一次，如何去除本弹框、如何获取商用授权、更多信息请看控制台日志",
			showCancel: false,
			confirmText: "我知道啦",
			complete: function() {
				call();
			}
		});
	}


	/**逻辑层调用本方法查找用于可视化绘制的canvas实例
		componentThis:this 当前页面或当前组件
		selectorList:[".class"] 要查找的canvas列表，提供css选择器，查找得到canvas1 canvas2...
		renderjsFunc:"eval js" App环境下在renderjs中进行绘制时执行的js代码，可以使用结果变量canvas1 canvas2...，代码中的this为renderjs模块的this
		mainFunc:fn(canvas1,canvas2...) 逻辑层中进行绘制时调用函数（H5、小程序等），参数为查找到的canvas

	App、H5会在原位置添加新的html canvas节点，并且隐藏老的canvas，原因是uni创建的canvas不可以使用：如果是隐藏的uni-canvas没法初始化，到显示的时候会篡改canvas已设置的宽高，App中uni-canvas显示有问题 似乎设置了缩放 导致显示不全，重新建一个省心还高效
	**/
	RecordApp.UniFindCanvas = function(componentThis, selectorList, renderjsFunc, mainFunc) {
		var vals = [];
		//微信小程序中进行绘制
		if (isMpWx) {
			var find = function(idx) {
				if (idx >= selectorList.length) {
					mainFunc.apply(componentThis, vals);
					return;
				}
				componentThis.createSelectorQuery().select(selectorList[idx])
					.fields({
						node: true
					}).exec(function(res) {
						vals.push(res[0].node);
						find(idx + 1);
					});
			};
			find(0);
			return;
		}
		//H5环境中绘制
		if (isH5) {
			for (var i = 0, L = selectorList.length; i < L; i++) { //@@Fast
				var cls = selectorList[i];
				var els = componentThis.$el.querySelectorAll(cls + " canvas"),
					el = els[0],
					el2 = els[1];
				if (!el) {
					CLog($T("k7im::未找到Canvas：{1}，请确保此DOM已挂载（可尝试用$nextTick等待DOM更新）", 0, cls), 1);
				} else {
					if (el2) {
						if (!el2.getAttribute("el2")) {
							el = els[1];
							el2 = els[0]
						}
						el2.parentNode.removeChild(el2);
					}
					el.style.display = "none"; //隐藏掉老的，用自己创建的原味canvas没bug
					el2 = document.createElement("canvas");
					el2.setAttribute("el2", "1");
					el2.style.width = el2.style.height = "100%";
					el.parentNode.appendChild(el2);
				}
				vals.push(el2);
			}
			mainFunc.apply(componentThis, vals);
			return;
		}
		//renderjs中进行绘制
		if (isAppMain) {
			var jsCode = [];
			for (var i = 0, L = selectorList.length; i < L; i++) { //@@Fast
				jsCode.push(`
				var cls="${selectorList[i]}";
				var els=this.$ownerInstance.$el.querySelectorAll(cls+" canvas"),el=els[0],el2=els[1];
				if(!el){
					RecordApp.CLog(Recorder.i18n.$T("dzX0::未找到Canvas：{1}，请确保此DOM已挂载（可尝试用$nextTick等待DOM更新）",0,cls),1);
				}else{
					if(el2){
						if(!el2.getAttribute("el2")){ el=els[1]; el2=els[0] }
						el2.parentNode.removeChild(el2);
					}
					el.style.display="none";
					el2=document.createElement("canvas");
					el2.setAttribute("el2","1"); el2.style.width=el2.style.height="100%";
					el.parentNode.appendChild(el2);
				}
				var canvas${i+1}=el2;
			`);
			}
			jsCode.push(renderjsFunc);
			RecordApp.UniWebViewVueCall(componentThis, jsCode.join("\n"));
			return;
		}
		//其他未适配的逻辑层
		CLog($T("yI24::RecordApp.UniFindCanvas未适配当前环境"), 1);
	};





	//绑定原生插件给js的回调
	var nativeUtsPluginBind = function() {
		nativeUtsPluginRequest("jsCall", {}, null, null, function(data) {
			var act = data.action;
			if (act == "onLog") { //日志输出
				if (!log1) CLog("[" + tag + " Info]" + data.info);
				log1 = 1; //输出一条插件信息
				if (data.isError) {
					CLog("[" + tag + "][" + data.tag + "]" + data.message, 1);
				} else {
					CLog("[" + tag + "][" + data.tag + "]" + data.message);
				}
			} else if (act == "onRecord") { //录音数据
				RecordApp.UniNativeRecordReceivePCM(data.pcmDataBase64, data.sampleRate);
			} else if (act == "noop") {
				//NOOP
			} else {
				CLog($T("dl4f::{1}回传了未知内容，", 0, tag) + "action=" + act, 3);
			}
		});

		var nup = RecordApp.UniNativeUtsPlugin,
			log1 = 0;
		var tag = nup && nup.nativePlugin ? loadNpName : "RecorderUtsPlugin";
		if (nup) RecordApp.__uniNbjc = 1;
	};
	var NpName0 = "Recorder-NativePlugin",
		NpDcUrl = "https://ext.dcloud.net.cn/plugin?name=" + NpName0,
		loadNpName = NpName0;

	//检查原生插件配置是否有效
	var nativeUtsPluginCheck = RecordApp.UniCheckNativeUtsPluginConfig = function() {
		var nup = RecordApp.UniNativeUtsPlugin,
			err = "";
		if (!nup || !isApp) return "";
		if (isBrowser) err = $T("l6sY::renderjs中不支持设置RecordApp.UniNativeUtsPlugin");

		if (!err && nup.nativePlugin) {
			if (!RecordApp.__uniNP) { //加载原生插件
				loadNpName = nup.nativePluginName || loadNpName;
				var np = 0,
					name = loadNpName;
				for (var i = 0; !np && i < 2; i++) {
					try {
						np = uni.requireNativePlugin(name)
					} catch (e) {} //加载插件失败不管
					if (!np && name == NpName0) { //尝试加载单端的包
						name = NpName0 + "-" + (isAppIOS ? "iOS" : "Android");
						CLog($T("kSjQ::当前App未打包进双端原生插件[{1}]，尝试加载单端[{2}]", 0, NpName0, name));
					} else break;
				}
				RecordApp.__uniNP = np;
				if (np) {
					CLog($T("Xh1W::已加载原生录音插件[{1}]", 0, name));
				} else {
					var name = loadNpName == NpName0 ? NpDcUrl : loadNpName;
					err = $T("SCW9::配置了RecordApp.UniNativeUtsPlugin，但当前App未打包进原生录音插件[{1}]", 0, name);
				}
			}
		} else if (!err && !nup.request) {
			err = $T("TGMm::提供的RecordApp.UniNativeUtsPlugin值不是RecordApp的uts原生录音插件");
		}
		if (err) {
			RecordApp.UniNativeUtsPlugin = null;
			CLog(err, 1);
		}
		RecordApp.__uniNupErr = err;
		return err;
	};

	//调用原生插件功能
	var nativeUtsPluginRequest = function(action, args, True, False, proc) {
		nativeUtsPluginCheck();
		var nup = RecordApp.UniNativeUtsPlugin;
		if (!nup) {
			var err = RecordApp.__uniNupErr || $T("H753::未配置RecordApp.UniNativeUtsPlugin原生录音插件");
			False && False(err);
			return;
		}
		var data = {
			action: action,
			args: args || {}
		};
		if (!proc) proc = function(json) {
			if (json.status != "success") {
				False && False(json.message);
				return;
			};
			True && True(json.value, json);
		};
		if (nup.nativePlugin) {
			RecordApp.__uniNP.request(data, proc);
		} else {
			nup.request(data, proc);
		}
	};
	//App环境下调用原生插件功能，异步返回调用结果，出错会抛异常
	RecordApp.UniNativeUtsPluginCallAsync = function(action, args) {
		return new Promise(function(resolve, reject) {
			if (!isAppMain) {
				return reject(new Error($T("MrBx::需在App逻辑层中调用原生插件功能")));
			};
			if (!RecordApp.__uniNbjc) { //未绑定插件回调就尽量绑定一下，方便显示插件日志
				nativeUtsPluginBind();
			};
			nativeUtsPluginRequest(action, args, function(val) {
				resolve(val);
			}, function(err) {
				reject(new Error(err));
			});
		})
	};






	/*******实现统一接口*******/
	platform.Install = function(success, fail) {
		LogCheckConfig();
		success();
	};
	//暂停和恢复录音功能转发给renderjs层执行，没有renderjs就执行默认的操作
	platform.Pause = function() {
		return pauseResume("Pause");
	}
	platform.Resume = function() {
		return pauseResume("Resume");
	}
	var pauseResume = function(key) {
		if (!UseWebView()) return false;
		var rec = onRecFn.rec;
		if (!rec) {
			CLog($T("0FGq::未开始录音，不可以调用{1}", 0, key), 3);
			return;
		};
		var actWvErr = Check_ActWv(1); //检查WebView是否是请求权限时的
		if (actWvErr) {
			CLog(actWvErr, 1);
			return
		}
		RecordApp.UniWebViewEval(WvCid_to_Cp(rec.__wvCid), 'RecordApp.' + key + '()');
	}




	platform.RequestPermission = function(sid, success, fail) {
		var old = onRecFn.rec;
		onRecFn.rec = null;
		if (old) { //清理未关闭的录音
			if (UseWebView()) {
				RecordApp.UniWebViewEval(WvCid_to_Cp(old.__wvCid), 'RecordApp.Stop()');
			}
		}
		if (UseWebView() && !RecordApp.__callWvActivate) {
			fail($T("PkQ2::需先调用RecordApp.UniWebViewActivate，然后才可以调用RequestPermission"));
			return;
		}

		lockReqWvCid();
		LogCheckConfig(true);
		LogCheckLicense(function() {
			RequestPermissionExec(sid, success, fail);
		});
	}
	var RequestPermissionExec = function(sid, success, fail) {
		//只有 原生插件、renderjs 组合出来的三种情况会调用本方法
		//=====【在renderjs中】=====只会是从逻辑层进行调用，此时只需对原生插件的数据进行处理
		if (isBrowser) {
			if (!RecordApp.UniAppUseNative) {
				fail($T("Jk72::不应当出现的非H5权限请求"));
				return;
			}
			success();
			return;
		}
		//=====【逻辑层中】=====
		//请求App录音权限
		var appReq = function(next) {
			if (isAppIOS) {
				CLog($T("Y3rC::正在调用plus.ios@AVAudioSession请求iOS原生录音权限"));
				var iosReq = function() {
					if (RecordApp.DisableIOSPlusReqPermission) { //iOS可以不用权限请求，WebView会自动处理权限
						next();
						return;
					}
					var audioSession = plus.ios.importClass("AVAudioSession").sharedInstance();
					var status = audioSession.recordPermission();
					if (status == 1970168948) { //undetermined
						audioSession.requestRecordPermission(iosReq);
					} else if (status == 1735552628) { // granted
						CLog($T("j15C::已获得iOS原生录音权限"));
						next();
					} else { //1684369017 denied
						CLog($T("iKhe::plus.ios请求录音权限，状态值: ") + "denied " + status, 1);
						fail($T("0caE::用户拒绝了录音权限"), true);
					}
					plus.ios.deleteObject(audioSession);
				}
				iosReq();
			} else {
				CLog($T("7Noe::正在调用plus.android.requestPermissions请求Android原生录音权限"));
				plus.android.requestPermissions(["android.permission.RECORD_AUDIO"], function(data) {
					if (data.granted.length > 0) {
						CLog($T("Bgls::已获得Android原生录音权限：") + JSON.stringify(data));
						next();
					} else {
						CLog($T("Ruxl::plus.android请求录音权限：无权限"), 1, data);
						fail($T("l7WP::用户拒绝了录音权限"), true);
					}
				}, function(e) {
					CLog($T("0JQw::plus.android请求录音权限出错：") + e.message, 1, e);
					fail($T("Mvl7::调用plus的权限请求出错：") + e.message);
				});
			}
		}
		//调用原生插件的录音权限
		var callNativeUtsPlugin = function(next) {
			nativeUtsPluginBind(); //绑定回调
			//只进行检查，原生插件可以不进行实际权限请求
			nativeUtsPluginRequest("recordPermission", {}, next, fail);
		};

		//在逻辑层中，且不需要renderjs支持，此时由原生插件提供支持
		if (RecordApp.UniWithoutAppRenderjs) {
			appReq(function() { //直接先用App自带的权限接口获取权限
				callNativeUtsPlugin(success);
			});
			return;
		}


		var reqCp = WvCid_to_Cp("@req");
		//检查renderjs是否准备好
		var checkRenderjs = function(next) {
			var actWvErr = Check_ActWv(1); //检查WebView是否是请求权限时的
			var errTag = $T("ksoA::无法调用RequestPermission：");
			if (actWvErr) {
				fail(errTag + actWvErr);
				return;
			}

			RecordApp.UniWebViewCallAsync(reqCp, {
				tag: errTag,
				timeout: 2000,
				useEval: true //这里不检测是否能访问到vue组件
			}, 'CallSuccess(1)').then(function() {
				next();
			}).catch(function(e) {
				fail(e.isTimeout ? errTag + $T("KnF0::无法连接到renderjs") : e.message);
			});
		};
		//调用renderjs中的录音权限
		var renderjsReq = function(native) {
			if (!RecordApp.__Sync(sid)) {
				fail("Incorrect sync status");
				return;
			};
			var h5Set = "RequestPermission_H5OpenSet",
				h5SetO = RecordApp[h5Set] || {};
			//执行renderjs中的权限请求，将环境准备好
			RecordApp.UniWebViewCallAsync(reqCp, { //同时检测是否能访问到vue组件
				timeout: -1
			}, `
			RecordApp.UniAppUseNative=${!!native};
			RecordApp.Current=null; //需先重置，不然native变化后install不一致
			RecordApp.${h5Set}=${JSON.stringify(h5SetO)};
			RecordApp.RequestPermission(function(){
				CallSuccess({ok:1});
			},function(errMsg,isUserNotAllow){
				CallSuccess({errMsg:errMsg,isUserNotAllow:isUserNotAllow});
			});
		`).then(function(val) {
				if (val.ok) success();
				else fail(val.errMsg, val.isUserNotAllow);
			}).catch(function(e) {
				fail(e.message);
			});
		};

		//在逻辑层中，需要renderjs支持，且有原生插件提供支持
		if (RecordApp.UniNativeUtsPlugin) {
			checkRenderjs(function() {
				appReq(function() { //直接先用App自带的权限接口获取权限
					callNativeUtsPlugin(function() {
						renderjsReq(true)
					});
				});
			});
			return;
		}
		//在逻辑层中，只由renderjs支持，使用H5录音
		checkRenderjs(function() {
			//请求app录音权限
			appReq(function() {
				renderjsReq()
			});
		});
	};





	platform.Start = function(sid, set, success, fail) {
		var old = onRecFn.rec;
		onRecFn.rec = null;
		if (old) { //清理未关闭的录音
			if (UseWebView()) {
				RecordApp.UniWebViewEval(WvCid_to_Cp(old.__wvCid), 'RecordApp.Stop()');
			}
		}
		if (UseWebView() && !RecordApp.__callWvActivate) {
			fail($T("XCMU::需先调用RecordApp.UniWebViewActivate，然后才可以调用Start"));
			return;
		}

		//只有 原生插件、renderjs 组合出来的三种情况会调用本方法
		onRecFn.param = set;
		var rec = Recorder(set);
		rec.set.disableEnvInFix = true; //不要音频输入丢失补偿
		rec.dataType = "arraybuffer";

		onRecFn.nativeToRjs = false; //逻辑层是否要将原生录音插件回传的数据转发给renderjs
		onRecFn.rec = rec; //等待第一个数据到来再调用rec.start
		RecordApp.__Rec = rec; //App需要暴露出使用到的rec实例

		//=====【在renderjs中】=====只会是从逻辑层进行调用，此时只需对原生插件的数据进行处理
		if (isBrowser) {
			if (!RecordApp.UniAppUseNative) {
				fail($T("rSLO::不应当出现的非H5录音Start"));
				return;
			}
			success();
			return;
		}
		//=====【逻辑层中】=====
		//调用原生插件的录音start
		var callNativeUtsPlugin = function(next) {
			var nSet = JSON.parse(JSON.stringify(copySet));
			nSet.nativeSampleRateTarget = nSet.nativeSampleRateTarget || 48000; //原生层返回数据的采样率
			nSet.nativeSampleRateRecSet = nSet.sampleRate;
			nSet.sampleRate = nSet.nativeSampleRateTarget; //要求原生层尽量按最大采样率返回数据

			var aec = (nSet.audioTrackSet || {}).echoCancellation;
			var aecE = nSet.appNativePlugin_AEC_Enable;
			if (aec && aecE == null) {
				//iOS只提供回声消除配置，Android配置后audioSource默认为7
				aecE = 1;
				nSet.appNativePlugin_AEC_Enable = true;
			}
			if (!isAppIOS && nSet.android_audioSource == null) {
				//Android指定麦克风源 MediaRecorder.AudioSource，0 DEFAULT 默认音频源，1 MIC 主麦克风，5 CAMCORDER 相机方向的麦，6 VOICE_RECOGNITION 语音识别，7 VOICE_COMMUNICATION 语音通信(带回声消除)
				nSet.android_audioSource = aecE ? 7 : RecordApp.Default_Android_AudioSource ||
				"0"; //提供此配置时优先级比audioTrackSet更高
			}
			CLog("Native Start Set:" + JSON.stringify(nSet));

			nativeUtsPluginBind(); //绑定回调
			nativeUtsPluginRequest("recordStart", nSet, function() {
				//激活定时心跳，如果超过10秒未发心跳，原生插件将会停止录音，防止未stop导致泄露
				RecordApp._X3Ij_alive = setInterval(function() {
					nativeUtsPluginRequest("recordAlive", {}, function() {});
				}, 5000);
				next();
			}, fail);
		};
		clearInterval(RecordApp._X3Ij_alive); //清理定时心跳

		var copySet = {};
		for (var k in set) {
			if (!/_renderjs$/.test(k)) {
				copySet[k] = set[k];
			}
		}
		copySet = JSON.parse(JSON.stringify(copySet));

		//在逻辑层中，且不需要renderjs支持，此时由原生插件提供支持
		if (RecordApp.UniWithoutAppRenderjs) {
			callNativeUtsPlugin(success);
			return;
		}

		rec.set.type = "unknown"; //禁用编码器，renderjs中进行编码
		//调用renderjs中的录音start
		var renderjsStart = function(next, native) {
			var actWvErr = Check_ActWv(1); //检查WebView是否是请求权限时的
			if (actWvErr) {
				fail($T("Bjx9::无法调用Start：") + actWvErr);
				return;
			}
			rec.__wvCid = GetCp_WvCid(null, "@req"); //记住当前页面标识
			onRecFn.nativeToRjs = native;

			var jsCode = [`var set=${JSON.stringify(copySet)};`];
			//实时回调将数据回传给逻辑层
			var procArgs6 = "buffers,power,duration,sampleRate,newIdx";
			jsCode.push(`var procFn=${set.onProcess_renderjs||0};
		var procBefore=${set.onProcessBefore_renderjs||0};
		var startFn=${set.start_renderjs||0};
		set.onProcess=function(${procArgs6}){
			procBefore&&procBefore.call(This,${procArgs6});
			var newBuffers=[],recSet=RecordApp.__Rec.set;
			for(var i=newIdx;i<buffers.length;i++)newBuffers.push(RecordApp.UniBtoa(buffers[i].buffer));//@@Fast
			RecordApp.UniWebViewSendToMain({action:"recProcess",recSet_sr:recSet.sampleRate,recSet_bit:recSet.bitRate,sampleRate:sampleRate,newBuffers:newBuffers});
			return procFn&&procFn.apply(This,arguments);
		};`);
			if (set.takeoffEncodeChunk || set.takeoffEncodeChunk_renderjs) {
				jsCode.push(`var takeFn=${set.takeoffEncodeChunk_renderjs||0};
			set.takeoffEncodeChunk=function(bytes){
				RecordApp.UniWebViewSendToMain({action:"recEncodeChunk",bytes:RecordApp.UniBtoa(bytes.buffer)});
				takeFn&&takeFn.apply(This,arguments);
			};`);
			}

			jsCode.push(`RecordApp.Start(set,function(){
			startFn&&startFn.call(This);
			CallSuccess();
		},function(errMsg){
			CallFail(errMsg);
		});`);

			RecordApp.UniWebViewCallAsync(WvCid_to_Cp(rec.__wvCid), {
				timeout: -1
			}, jsCode.join("\n")).then(function() {
				next();
			}).catch(function(e) {
				fail(e.message);
			});
		};
		//在逻辑层中，需要renderjs支持，且有原生插件提供支持
		if (RecordApp.UniNativeUtsPlugin) {
			renderjsStart(function() {
				if (!RecordApp.__Sync(sid)) {
					fail("Incorrect sync status");
					return;
				};
				callNativeUtsPlugin(success);
			}, true);
			return;
		}
		//在逻辑层中，只由renderjs支持，使用H5录音
		renderjsStart(success);
	};
	//App逻辑层Start时要禁用检查，不然不允许使用takeoffEncodeChunk
	platform.Start_Check = function(set) {
		if (!UseWebView()) return false;
		return "";
	};
	//非App时清理掉set里面不需要的参数
	platform.AllStart_Clean = function(set) {
		if (!UseWebView()) {
			for (var k in set) { //非App清理掉renderjs相关的配置
				if (/_renderjs$/.test(k)) {
					delete set[k];
				}
			}
		}
	};
	//renderjs回传给逻辑层的实时数据
	var onRecProcess = function(args) {
		var rec = onRecFn.rec;
		if (rec) { //把可能变更的值写回去
			rec.set.sampleRate = args.recSet_sr;
			rec.set.bitRate = args.recSet_bit;
		}
		var newBuffers = args.newBuffers;
		for (var i = 0, L = newBuffers.length; i < L; i++) { //@@Fast
			onRecFn(newBuffers[i], args.sampleRate);
		}
	}
	var onRecEncodeChunk = function(args) {
		var rec = onRecFn.rec;
		if (!rec) {
			CLog($T("MTdp::未开始录音，但收到renderjs回传的onRecEncodeChunk"), 3);
			return;
		}
		var bytes = new Uint8Array(UniAtob(args.bytes));
		rec.set.takeoffEncodeChunk && rec.set.takeoffEncodeChunk(bytes);
	}
	/*
	pcmDataBase64: base64<Int16[]>字符串 当前单声道录音缓冲PCM片段，正常情况下为上次回调本接口开始到现在的录音数据，Int16[]二进制数组需要编码成base64字符串；或者直接传一个Int16Array对象
	sampleRate：123456 录制音频实际的采样率
	*/
	var onRecFn = function(pcmDataBase64, sampleRate) {
		var rec = onRecFn.rec;
		if (!rec) {
			CLog($T("BjGP::未开始录音，但收到Uni Native PCM数据"), 3);
			return;
		};
		if (!rec._appStart) {
			rec.envStart({
				envName: platform.Key,
				canProcess: platform.CanProcess()
			}, sampleRate);
		};
		rec._appStart = 1;

		if (pcmDataBase64 instanceof Int16Array) {
			var pcm = new Int16Array(pcmDataBase64);
		} else {
			var pcm = new Int16Array(UniAtob(pcmDataBase64));
		}
		var sum = JsSource.pcm_sum(pcm);
		/*var sum=0;
		f o r(var i=0;i<pcm.length;i++){
			sum+=Math.abs(pcm[i]);
		}*/

		rec.envIn(pcm, sum);
	};
	RecordApp.UniNativeRecordReceivePCM = function(pcmDataBase64, sampleRate) {
		if (onRecFn.nativeToRjs) {
			//逻辑层将原生录音插件回传的数据转发给renderjs
			var rec = onRecFn.rec;
			if (!rec) {
				CLog($T("byzO::未开始录音，但收到UniNativeUtsPlugin PCM数据"), 3);
				return;
			};
			RecordApp.UniWebViewEval(WvCid_to_Cp(rec.__wvCid), 'RecordApp.UniNativeRecordReceivePCM("' +
				pcmDataBase64 + '",' + sampleRate + ')');
			return;
		}
		onRecFn(pcmDataBase64, sampleRate);
	};



	platform.Stop = function(sid, success, fail) {
		//只有 原生插件、renderjs 组合出来的三种情况会调用本方法
		var failCall = function(msg) {
			if (RecordApp.__Sync(sid)) {
				onRecFn.rec = null;
				if (rec && clearRjs && UseWebView())
					RecordApp.UniWebViewEval(WvCid_to_Cp(rec.__wvCid), 'RecordApp.Stop()');
			}
			fail(msg);
		};
		var rec = onRecFn.rec,
			clearRjs = true;
		var clearMsg = success ? "" : RecordApp.__StopOnlyClearMsg();
		var stopCall = function() {
			if (!RecordApp.__Sync(sid)) {
				failCall("Incorrect sync status");
				return;
			};
			onRecFn.rec = null;

			if (!rec) {
				failCall($T("YP4V::未开始录音") + (clearMsg ? " (" + clearMsg + ")" : ""));
				return;
			};

			CLog("rec encode: pcm:" + rec.recSize + " srcSR:" + rec.srcSampleRate + " set:" + JSON
				.stringify(onRecFn.param));

			if (!success) {
				end();
				failCall(clearMsg);
				return;
			};
			rec.stop(function(arrBuf, duration, mime) {
				end();
				success(arrBuf, duration, mime);
			}, function(msg) {
				end();
				failCall(msg);
			});
		};
		var end = function() {
			if (RecordApp.__Sync(sid)) {
				onRecFn.rec = null;
				//把可能变更的配置写回去
				for (var k in rec.set) {
					onRecFn.param[k] = rec.set[k];
				};
			};
		};

		//=====【在renderjs中】=====只会是从逻辑层进行调用，此时只需对原生插件的数据进行处理
		if (isBrowser) {
			if (!RecordApp.UniAppUseNative) {
				failCall($T("TPhg::不应当出现的非H5录音Stop"));
				return;
			}
			stopCall();
			return;
		}
		//=====【逻辑层中】=====
		//调用原生插件的录音stop
		var callNativeUtsPlugin = function(next) {
			nativeUtsPluginRequest("recordStop", {}, next, failCall);
		};
		clearInterval(RecordApp._X3Ij_alive); //关掉定时心跳

		//在逻辑层中，且不需要renderjs支持，此时由原生插件提供支持
		if (RecordApp.UniWithoutAppRenderjs) {
			callNativeUtsPlugin(stopCall);
			return;
		}

		//调用renderjs中的录音stop
		var renderjsStop = function(native) {
			if (!rec) {
				failCall($T("pP4O::未开始录音") + (clearMsg ? " (" + clearMsg + ")" : ""));
				return;
			};
			var actWvErr = Check_ActWv(1); //检查WebView是否是请求权限时的
			if (actWvErr) {
				failCall($T("H6cq::无法调用Stop：") + actWvErr);
				return;
			}

			var jsCode = `(function(){
			var stopFn=${success&&onRecFn.param.stop_renderjs||0};
			var clear=${!success};
			var errFn=function(errMsg){
				CallFail(errMsg);
			};
			RecordApp.Stop(clear?null:function(arrBuf,duration,mime){
				stopFn&&stopFn.apply(This,arguments);
				var recSet=RecordApp.__Rec.set,t1=Date.now();
				RecordApp.CLog("开始传输"+arrBuf.byteLength+"字节的数据回逻辑层，可能会比较慢，推荐使用takeoffEncodeChunk实时获取音频文件数据可避免Stop时产生超大数据回传");
				RecordApp.UniWebViewSendBigBytesToMain(arrBuf,function(dataId){//数据可能很大
					RecordApp.CLog("完成传输"+arrBuf.byteLength+"字节的数据回逻辑层，耗时"+(Date.now()-t1)+"ms");
					CallSuccess({recSet_sr:recSet.sampleRate,recSet_bit:recSet.bitRate,dataId:dataId,duration:duration,mime:mime});
				},errFn);
			},errFn);
		})()`;

			RecordApp.UniWebViewCallAsync(WvCid_to_Cp(rec.__wvCid), {
				timeout: -1
			}, jsCode).then(function(val) {
				clearRjs = false;
				rec.set.type = onRecFn.param.type; //开始录音时被设为了unknown 禁用了编码器
				rec.set.sampleRate = val.recSet_sr;
				rec.set.bitRate = val.recSet_bit;
				end();

				var arrBuf = RecordApp.UniMainTakeBigBytes(val.dataId);
				if (!arrBuf) {
					failCall($T("gomD::不应该出现的renderjs发回的文件数据丢失"));
					return;
				}
				success(arrBuf, val.duration, val.mime);
			}).catch(function(e) {
				clearRjs = false;
				failCall(e.message);
			});
		}
		//在逻辑层中，需要renderjs支持，且有原生插件提供支持
		if (RecordApp.UniNativeUtsPlugin) {
			callNativeUtsPlugin(function() {
				if (!RecordApp.__Sync(sid)) {
					failCall("Incorrect sync status");
					return;
				};
				renderjsStop(true);
			});
			return;
		}
		//在逻辑层中，只由renderjs支持，使用H5录音
		renderjsStop();
	};

	/*@@Source End@@*/
}));